﻿#region Using directives

using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;

using Weazel.Gezel.Model;
using Weazel.Gezel.Model.Errors;
using GezelModel = Weazel.Gezel.Model.Model;

using Weazel.Gezel.Parser;
using Weazel.Gezel.Conversions;
using Weazel.Gezel.Conversions.Model;
using Weazel.Gezel.Conversions.CodeGeneration;
using SoftwareType = Weazel.Gezel.Conversions.Model.Type;

#endregion

namespace Weazel.Gezel.Simulator.Optimized
{
  static class Program
  {
    private static string filename;
    private static string outputDir;
    private static string language;

    public static void Main(string[] arguments)
    {
      //try
      //{

				parseCommandLine(arguments);

				GezelModel model = getGezelModel();

        if(language == "cs")
				  generateCSharpSimulation(model);
        else if(language == "cpp")
				  generateIsoCppSimulation(model);

      //}
      //catch (Exception ex)
      //{
      //  Console.Error.WriteLine("Software conversion aborted with the following error:");
      //  Console.Error.WriteLine(ex.Message);
      //  Console.Error.WriteLine();
      //  Console.Error.WriteLine(ex.StackTrace);
      //}
    }

    private static void generateCSharpSimulation(GezelModel model)
    {
      SourceFile[] sourceFiles = new ModelConverter().ConvertModel(model, TargetLanguage.CSharp);

      const string csHeaderLine =
          "using System;\nusing System.IO;\nusing System.Diagnostics;\n" +
          "using System.Collections.Generic;\n\nusing Weazel.Math;\n\n";

      writeSourceFiles(sourceFiles, "cs", csHeaderLine);
    }

    private static void generateIsoCppSimulation(GezelModel model)
    {
      SourceFile[] sourceFiles = new ModelConverter().ConvertModel(model, TargetLanguage.IsoCpp);

      // sort 
      List<SourceFile> headers = new List<SourceFile>();
      List<SourceFile> codefiles = new List<SourceFile>();
      List<SourceFile> tracersCodeFiles = new List<SourceFile>();
      List<SourceFile> displaysCodeFiles = new List<SourceFile>();

      const string isoCppHeaderLine =
          "#include <stdio.h>\n" +  
          "#include \"Definitions.h\"\n" +
          "#include \"Simulator.h\"\n" +
          "#include \"Outports.h\"\n" +
          "#include \"Inports.h\"\n" +
          "#include \"Controllers.h\"\n" +
          "#include \"Signals.h\"\n" +
          "#include \"Registers.h\"\n" +
          "#include \"Tracers.h\"\n" +
          "#include \"Displays.h\"\n" +
					"#include \"OutportDynamicList.h\"\n\n" +
          "using namespace std;\n\n";
      const string tracerHeaderLine =
          "#ifdef _MSC_VER\n" +
          "#define _Tracers_cpp_\n" +
          "#include \"Definitions.h\"\n" +
          "#include \"Simulator.h\"\n" +
          "#include \"Outports.h\"\n" +
          "#include \"Inports.h\"\n" +
          "#include \"Controllers.h\"\n" +
          "#include \"Signals.h\"\n" +
          "#include \"Registers.h\"\n" +
          "#include \"Tracers.h\"\n" +
          "#include \"Displays.h\"\n" +
					"#include \"OutportDynamicList.h\"\n\n" +
          "using namespace std;\n\n" +
          "// TODO: determine buffer size from width of signal being traced\n" +
          "#define BUFFER_SIZE 1024\n" +
          "char TraceBuffer[BUFFER_SIZE];\n\n";
      const string displayHeaderLine =
          "#define _Tracers_cpp_\n" +
          "#include \"Definitions.h\"\n" +
          "#include \"Simulator.h\"\n" +
          "#include \"Outports.h\"\n" +
          "#include \"Inports.h\"\n" +
          "#include \"Controllers.h\"\n" +
          "#include \"Signals.h\"\n" +
          "#include \"Registers.h\"\n" +
          "#include \"Tracers.h\"\n" +
          "#include \"Displays.h\"\n" +
					"#include \"OutportDynamicList.h\"\n\n" +
          "using namespace std;\n\n" +
          "// TODO: determine buffer size from width of signal being traced\n" +
          "#define BUFFER_SIZE 1024\n" +
          "char DisplayBuffer[BUFFER_SIZE];\n\n";
      const string includeGuardEnd = "\n#endif // include guard\n";

      for (int i = 0; i < sourceFiles.Length; i++)
      {
        SourceFile file = sourceFiles[i];
        if (file.IsHeader && file.Type.Name.StartsWith("Trace_"))
        {
          string includeGuardStart =
              string.Format(
                  "#ifdef _MSC_VER\n#ifndef _{0}_h_\n#define _{0}_h_\n{1}",
                  file.Type.Name,
                  isoCppHeaderLine
              );
          file.Code = includeGuardStart + file.Code + includeGuardEnd + includeGuardEnd;
          headers.Add(file);
        }
        else if (file.IsHeader)
        {
          string includeGuardStart =
              string.Format(
                  "#ifndef _{0}_h_\n#define _{0}_h_\n{1}",
                  file.Type.Name,
                  isoCppHeaderLine
              );
          file.Code = includeGuardStart + file.Code + includeGuardEnd;
          headers.Add(file);
        }
        else if (file.Type.Name.StartsWith("Trace_"))
        {
          tracersCodeFiles.Add(file);
        }
        else if (file.Type.Name.StartsWith("Display_"))
        {
          displaysCodeFiles.Add(file);
        }
        else
        {
          codefiles.Add(file);
        }
      }

      writeSourceFiles(headers.ToArray(), "h", string.Empty);
      writeSourceFiles(codefiles.ToArray(), "cpp", isoCppHeaderLine);

      StringBuilder tracersCodeBuffer = new StringBuilder(tracerHeaderLine);
      foreach (SourceFile file in tracersCodeFiles)
        tracersCodeBuffer.Append(file.Code);

      tracersCodeBuffer.Append(includeGuardEnd);

      writeFile(Path.Combine(outputDir, "Tracers.cpp"), tracersCodeBuffer.ToString());

      StringBuilder displaysCodeBuffer = new StringBuilder(displayHeaderLine);
      foreach (SourceFile file in displaysCodeFiles)
        displaysCodeBuffer.Append(file.Code);

      writeFile(Path.Combine(outputDir, "Displays.cpp"), displaysCodeBuffer.ToString());
    }

    private static void writeSourceFiles(SourceFile[] sourceFiles, string extension, string headerLine)
    {
      StringBuilder tracersCode = new StringBuilder(headerLine);
      StringBuilder outportsCode = new StringBuilder(headerLine);
      StringBuilder inportsCode = new StringBuilder(headerLine);
      StringBuilder registersCode = new StringBuilder(headerLine);
      StringBuilder controllersCode = new StringBuilder(headerLine);
      StringBuilder signalsCode = new StringBuilder(headerLine);
      StringBuilder displaysCode = new StringBuilder(headerLine);

      string simulatorCode = null;

      foreach (SourceFile sourceFile in sourceFiles)
      {
        if (sourceFile.Type is OutPortType)
        {
          outportsCode.AppendLine(sourceFile.Code);
        }
        else if (sourceFile.Type is InPortType)
        {
          inportsCode.AppendLine(sourceFile.Code);
        }
        else if (sourceFile.Type is RegisterType)
        {
          registersCode.AppendLine(sourceFile.Code);
        }
        else if (sourceFile.Type is ControllerType)
        {
          controllersCode.AppendLine(sourceFile.Code);
        }
        else if (sourceFile.Type is TracerType)
        {
          tracersCode.AppendLine(sourceFile.Code);
        }
        else if (sourceFile.Type is SignalType)
        {
          signalsCode.AppendLine(sourceFile.Code);
        }
        else if (sourceFile.Type is DisplayType)
        {
          displaysCode.AppendLine(sourceFile.Code);
        }
        else if (sourceFile.Type is SimulatorType)
        {
          Debug.Assert(simulatorCode == null);
          simulatorCode = headerLine + sourceFile.Code;
        }
      }

      writeFile(Path.Combine(outputDir, "Outports." + extension), outportsCode.ToString());
      writeFile(Path.Combine(outputDir, "Inports." + extension), inportsCode.ToString());
      writeFile(Path.Combine(outputDir, "Registers." + extension), registersCode.ToString());
      writeFile(Path.Combine(outputDir, "Controllers." + extension), controllersCode.ToString());
      writeFile(Path.Combine(outputDir, "Signals." + extension), signalsCode.ToString());
      writeFile(Path.Combine(outputDir, "Displays." + extension), displaysCode.ToString());
      writeFile(Path.Combine(outputDir, "Tracers." + extension), tracersCode.ToString());
      writeFile(Path.Combine(outputDir, "Simulator." + extension), simulatorCode);
    }

    private static void writeFile(string filename, string contents)
    {
      Console.Error.WriteLine("Writing file '{0}'", new FileInfo(filename).Name);

      using (StreamWriter writer = new StreamWriter(filename))
      {
        writer.Write(contents);
      }
    }

    private static GezelModel getGezelModel()
    {
      string description = null;

      using (StreamReader reader = new StreamReader(filename))
      {
        description = reader.ReadToEnd();
      }

      // setup and run simulation
      // parse the file
      List<ParseError> parseErrors = new List<ParseError>();
      GezelModel model = ParseModel.Parse(ref parseErrors, description, filename);


      // print out any parse errors
      if (parseErrors.Count > 0)
      {
        Console.WriteLine("Simulation was aborted because of these parse error(s):");
        foreach (ParseError error in parseErrors)
        {
          Console.WriteLine();
          Console.WriteLine(error.ToString());
        }

        // if the model contained errors
        // don't try to setup simulation
        Environment.Exit(1);
      }

      // verify the file
      List<VerificationError> verificationErrors = new List<VerificationError>();
      List<VerificationWarning> verificationWarnings = new List<VerificationWarning>();
      model.Verify(ref verificationErrors, ref verificationWarnings);

      // print errors if any
      if (verificationErrors.Count > 0)
      {
        Console.WriteLine("Simulation was aborted because of the these verification error(s):");
        foreach (VerificationError error in verificationErrors)
        {
          Console.WriteLine();
          Console.WriteLine(error.ToString());
        }

        // if the model contained errors
        // don't try to setup simulation
        Environment.Exit(1);
      }

      // print out warnings if any
      if (verificationWarnings.Count > 0)
      {
        Console.WriteLine("The following warnings were issued during verification:");
        foreach (VerificationWarning warning in verificationWarnings)
        {
          Console.WriteLine();
          Console.WriteLine(warning.ToString());
        }
      }

      return model;
    }

    private static void parseCommandLine(string[] arguments)
    {
      // handle command line arguments
      if (arguments.Length < 2 || arguments.Length > 3)
      {
        Console.Error.WriteLine("Wrong number of arguments");
        usage();
      }

      filename = arguments[0];
      language = arguments[1];

      if (language != "cpp" && language != "cs")
      {
        Console.Error.WriteLine("Invalid language: '{0}'", language);
        usage();
      }

      if (arguments.Length == 2)
        outputDir = Environment.CurrentDirectory;
      else
        outputDir = arguments[2];

      if (!File.Exists(filename))
      {
        Console.Error.WriteLine("Can't find file {0}", filename);
        usage();
      }

      if (!Directory.Exists(outputDir))
      {
        // user has specified directory
        Debug.Assert(outputDir != Environment.CurrentDirectory);

        Console.Error.WriteLine("Directory {0} does not exist", outputDir);
        usage();
      }
    }

    /// <summary>
    /// Show a brief usage message and exit with exitcode 1 (error).
    /// </summary>
    private static void usage()
    {
      // get the name of the executable
      Assembly asm = Assembly.GetEntryAssembly();
      string executableName = asm.GetName().Name;

      Console.Error.WriteLine("\nUsage:\n\n{0} <gezel model> <language> [output path]\n", executableName);
      Console.Error.WriteLine("\nWhere:\n");
      Console.Error.WriteLine("gezel model\tspecifies gezel model to generate simulation for");
      Console.Error.WriteLine("language\tspecifies output language, can be cs or cpp");
      Console.Error.WriteLine("output path\tspecifies where output files are to be stored");

      Environment.Exit(1);
    }
  }
}
